use super::lexer::*;
use crate::object::*;

use std::error::Error;
use std::fmt;
use std::rc::Rc;

#[derive(Debug)]
pub struct ParseError {
    err: String,
}

impl Error for ParseError {}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Parse error: {}", self.err)
    }
}

pub fn parse(program: &str) -> Result<Rc<Object>, Box<dyn Error>> {
    let tokens = tokenize(program)?;
    parse_tokens(&tokens)
}

fn parse_tokens(tokens: &[Token]) -> Result<Rc<Object>, Box<dyn Error>> {
    match tokens.len() {
        0 => Ok(VOID_OBJ.clone()),
        1 => match &tokens[0] {
            Token::Integer(n) => Ok(Rc::new(Object::Integer(*n))),
            Token::Float(n) => Ok(Rc::new(Object::Float(*n))),
            Token::String(s) => Ok(Rc::new(Object::String(s.to_objstring()))),
            Token::Symbol(s) => Ok(Rc::new(Object::Symbol(s.to_objstring()))),
            _ => Err(Box::new(ParseError {
                err: format!("Token {:?} can't be evaluated", &tokens[0]),
            })),
        },
        _ => {
            // Must begin with LParen
            if tokens[0] != Token::LParen {
                return Err(Box::new(ParseError {
                    err: format!("Expected LParen, found {:?}", tokens[0]),
                }));
            }

            let (object, _) = parse_list_tokens(&tokens[1..])?;
            Ok(object)
        }
    }
}

fn parse_list_tokens<'a>(
    mut tokens: &'a [Token<'a>],
) -> Result<(Rc<Object>, &'a [Token<'a>]), Box<dyn Error>> {
    let mut list: Vec<Rc<Object>> = Vec::new();

    // Until meet RParen
    loop {
        // We still not get a RParen, So the tokens can't not be finish
        if tokens.len() == 0 {
            return Err(Box::new(ParseError {
                err: format!("Did not find enough tokens"),
            }));
        }

        match &tokens[0] {
            Token::Keyword(k) => {
                list.push(keyword_obj(k).unwrap());
                tokens = &tokens[1..];
            }
            Token::Operator(b) => {
                list.push(operator_obj(b).unwrap());
                tokens = &tokens[1..];
            }
            Token::Integer(n) => {
                list.push(Rc::new(Object::Integer(*n)));
                tokens = &tokens[1..];
            }
            Token::Float(f) => {
                list.push(Rc::new(Object::Float(*f)));
                tokens = &tokens[1..];
            }
            Token::String(s) => {
                list.push(Rc::new(Object::String(s.to_objstring())));
                tokens = &tokens[1..];
            }
            Token::Symbol(s) => {
                list.push(Rc::new(Object::Symbol(s.to_objstring())));
                tokens = &tokens[1..];
            }
            Token::LParen => {
                let (object, rest_tokens) = parse_list_tokens(&tokens[1..])?;
                list.push(object);
                tokens = rest_tokens;
            }
            Token::RParen => return Ok((Rc::new(Object::CodeList(list)), &tokens[1..])),
            Token::EOF => unreachable!("No EOF will be add to tokens"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        let list = parse("(+ 1 2)").unwrap();
        assert_eq!(
            list,
            Rc::new(Object::CodeList(
                vec![
                    Rc::new(Object::Operator("+")),
                    Rc::new(Object::Integer(1)),
                    Rc::new(Object::Integer(2)),
                ]
                .into()
            ))
        );
    }

    #[test]
    fn test_area_of_a_circle() {
        let program = "(
                         (define r 10)
                         (define pi 314)
                         (* pi (* r r))
                       )";
        let list = parse(program).unwrap();
        assert_eq!(
            list,
            Rc::new(Object::CodeList(
                vec![
                    Rc::new(Object::CodeList(
                        vec![
                            Rc::new(Object::Keyword("define")),
                            Rc::new(Object::Symbol("r".to_objstring())),
                            Rc::new(Object::Integer(10)),
                        ]
                        .into()
                    )),
                    Rc::new(Object::CodeList(
                        vec![
                            Rc::new(Object::Keyword("define")),
                            Rc::new(Object::Symbol("pi".to_objstring())),
                            Rc::new(Object::Integer(314)),
                        ]
                        .into()
                    )),
                    Rc::new(Object::CodeList(
                        vec![
                            Rc::new(Object::Operator("*")),
                            Rc::new(Object::Symbol("pi".to_objstring())),
                            Rc::new(Object::CodeList(
                                vec![
                                    Rc::new(Object::Operator("*")),
                                    Rc::new(Object::Symbol("r".to_objstring())),
                                    Rc::new(Object::Symbol("r".to_objstring())),
                                ]
                                .into()
                            )),
                        ]
                        .into()
                    )),
                ]
                .into()
            ))
        );
    }
}
